package com.pajk.user.service.common.http;

import java.io.IOException;
import java.nio.charset.Charset;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;

import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.pajk.user.util.HttpsUtil;

/**
 * Created by chenhaihua on 14-4-17.
 */
public class HttpsChannel {
    private static final Logger log = LoggerFactory.getLogger(HttpsChannel.class);

    private String              keyStorePassword;
    private String              trustStorePassword;
    private String              keyStoreType;
    private String              keyStorePath;
    private String              trustStorePath;

    private CloseableHttpClient httpClient;

    public void init() throws Exception {
        System.setProperty("java.protocol.handler.pkgs", "sun.net.www.protocol");
        SSLContext sslcontext = SSLContexts
            .custom()
            .loadKeyMaterial(
                HttpsUtil.getKeyStore(keyStoreType, keyStorePassword, keyStorePath),
                keyStorePassword.toCharArray())
            .loadTrustMaterial(
                HttpsUtil.getTrustStore(trustStorePassword, trustStorePath))
            .build();

        X509HostnameVerifier hv = new X509HostnameVerifier() {
            public boolean verify(String urlHostName, SSLSession session) {
                log.info("URL Host: " + session.getPeerHost());
                return true;
            }

            public void verify(String host, SSLSocket ssl) throws IOException {
            }

            public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
            }

            public void verify(String host, X509Certificate cert) throws SSLException {
            }
        };

        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, hv);

        PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(
            RegistryBuilder.<ConnectionSocketFactory> create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", sslsf).build());
        poolingmgr.setMaxTotal(40); //所有的host,连接池最大值
        poolingmgr.setDefaultMaxPerRoute(20); //针对单个host,链接池的最大值

        SocketConfig sconfig = SocketConfig.custom().setSoTimeout(3 * 1000) //一次http请求最大时间，设置3秒
            .build();

        poolingmgr.setDefaultSocketConfig(sconfig);

        RequestConfig rconfig = RequestConfig.custom().setConnectTimeout(3 * 1000) //连接host的最大时间，设置6秒
            .setConnectionRequestTimeout(60 * 1000) //从链接池中，去一个连接的最大时间，设置60秒
            .build();

        httpClient = HttpClients.custom().setConnectionManager(poolingmgr)
            .setDefaultRequestConfig(rconfig).build();

    }

    public Document call(String url, String params) {
        String response=null;
        try {
            response=excute(url, params);
            return DocumentHelper.parseText(response);
        } catch (DocumentException e) {
            log.info("httpsChannel parse Error: {} ",response);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public String excute(String url, String params) {
        CloseableHttpResponse response = null;
        try {
            HttpPost post = new HttpPost(url);
            post.setEntity(new StringEntity(params));
            long startTime = System.currentTimeMillis();
            response = httpClient.execute(post);
            long endTime=System.currentTimeMillis() - startTime;
            log.info("httpsChannel cost: {} {} ",url,endTime);
            //链接返回连接池方法就是消费它，可以认为就是打开一个网页，一旦流读完了，就把链接返回池中，
            //我认为该操作不用写在finally里面，client.execute(post)出错的话，会马上关闭该链接

            if (response.getStatusLine().getStatusCode() == 200) {
                HttpEntity entity=response.getEntity();
                ContentType contentType = ContentType.getOrDefault(entity);
                Charset charSet = contentType.getCharset();
                byte[] res = EntityUtils.toByteArray(entity);
                // return new String(res, "GBK");
                return new String(res, charSet);
            } else {
                throw new RuntimeException("http response:"
                                           + response.getStatusLine().getStatusCode());
            }
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            if (response != null) {
                try {
                    EntityUtils.consume(response.getEntity());
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        //连接关闭方式，连接将不会返回池中，直接关闭
        //response.close();
    }

    public void quit(){
        if (httpClient != null){
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void setKeyStorePassword(String keyStorePassword) {
        this.keyStorePassword = keyStorePassword;
    }

    public void setTrustStorePassword(String trustStorePassword) {
        this.trustStorePassword = trustStorePassword;
    }

    public void setKeyStoreType(String keyStoreType) {
        this.keyStoreType = keyStoreType;
    }

    public void setKeyStorePath(String keyStorePath) {
        this.keyStorePath = keyStorePath; }

    public void setTrustStorePath(String trustStorePath) {
        this.trustStorePath = trustStorePath; }
}
